﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
//using Lilium.Collections;
using System.Linq;
using Lilium.LParse.Memoization;

namespace Lilium.LParse
{
    public static class ParserCombinators
    {
        public static Parser<TToken, TResult> Between<TToken, TOpen, TClose, TResult>(this Parser<TToken, TResult> parser, Parser<TToken, TOpen> open, Parser<TToken, TClose> close)
        {
            return from _1 in open
                   from result in parser
                   from _2 in close
                   select result;
        }

        public static Parser<TToken, TResult2> Bind<TToken, TResult1, TResult2>(
            this Parser<TToken, TResult1> parser,
            Func<TResult1, Parser<TToken, TResult2>> selector)
        {
            return ParserMonad.SelectMany(parser, selector);
        }

        public static Parser<TToken, TResult> Choise<TToken, TResult>(params Parser<TToken, TResult>[] parsers)
        {
            return state =>
            {
                foreach (var parser in parsers)
                {
                    var result = parser(state);
                    if (!state.Failed)
                        return result;
                }

                return default(TResult);
            };
        }

        public static Parser<TToken, TResult> Choise<TToken, TResult>(Parser<TToken, TResult>[] parsers, string label)
        {
            return state =>
                {
                    state.SuspendValuesCollection();

                    foreach (var parser in parsers)
                    {
                        var result = parser(state);
                        if (!state.Failed)
                        {
                            state.ResumeValuesCollection();
                            return result;
                        }
                    }

                    state.ResumeValuesCollection(label);
                    return default(TResult);
                };
        }

        public static Parser<TToken, TResult> FoldMany<TToken, TItem, TResult>(
            this Parser<TToken, TItem> parser,
            Func<TResult, TItem, TResult> func,
            TResult seed)
        {
            return state =>
            {
                var current = parser(state);
                while (!state.Failed)
                {
                    seed = func(seed, current);
                    current = parser(state);
                }
                state.Restore();
                return seed;
            };
        }



        public static Parser<TToken, TResult> FoldMany1<TToken, TItem, TResult>(
            this Parser<TToken, TItem> parser,
            Func<TResult, TItem, TResult> func,
            TResult seed)
        {
            return state =>
            {
                var current = parser(state);

                if (state.Failed)
                    return seed; // тут надо default возвращать наверное..

                do
                {
                    seed = func(seed, current);
                    current = parser(state);
                } while (!state.Failed);

                state.Restore();
                return seed;
            };
        }

        public static Parser<TToken, TResult> Fold1Count<TToken, TResult>(this Parser<TToken, TResult> parser, int n, Func<TResult, TResult, TResult> func)
        {
            return state =>
            {
                if (n > 0)
                {
                    var accum = parser(state);

                    if (state.Failed)
                        return default(TResult);

                    while (--n != 0)
                    {
                        var current = parser(state);

                        if (state.Failed)
                            return default(TResult);

                        accum = func(accum, current);
                    }

                    return accum;
                }

                return default(TResult);
            };
        }

        public static Parser<TToken, TResult> Fold1Count<TToken, TResult>(this Parser<TToken, TResult> parser, int min, int max, Func<TResult, TResult, TResult> func)
        {
            if (min > max)
                throw new ArgumentException("min can't be grater than max");

            return state =>
            {
                if (max > 0)
                {
                    var accum = parser(state);

                    if (state.Failed)
                    {
                        if (min != 0)
                            state.Fail();

                        return accum;
                    }

                    var counter = 1;

                    while (--max != 0)
                    {
                        var current = parser(state);

                        if (state.Failed)
                        {
                            if (counter >= min)
                            {
                                state.Restore();
                                return accum;
                            }
                            else
                            {
                                return default(TResult);
                            }
                        }

                        accum = func(accum, current);
                        counter++;
                    }

                    return accum;
                }

                return default(TResult);
            };
        }

        public static Parser<TToken, TResult> Fold1Many1<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, TResult, TResult> func)
        {
            return state =>
            {
                var accum = parser(state);

                if (state.Failed)
                    return default(TResult);

                var current = parser(state);

                while (!state.Failed)
                {
                    accum = func(accum, current);
                    current = parser(state);
                }

                state.Restore();
                return accum;
            };
        }

        /// <summary>
        /// Applies parser without consuming any input.
        /// </summary>
        /// <typeparam name="TToken"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <returns></returns>
        public static Parser<TToken, TResult> LookAhead<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                var bookmark = state.CreateBookmark();

                var result = parser(state);

                state.RestoreBookmark(bookmark);

                return result;
            };
        }

        public static Parser<TToken, ICollection<TResult>> Many<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                var result = parser(state);

                if (!state.Failed)
                {
                    var results = new List<TResult>();
                    do
                    {
                        results.Add(result);
                        result = parser(state);
                    } while (!state.Failed);

                    state.Restore();
                    return new ReadOnlyCollection<TResult>(results);
                }

                state.Restore();
                return Utils.EmptyCollection<TResult>();
            };
        }

        public static Parser<TToken, ICollection<TResult>> Many1<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                var result = parser(state);

                if (!state.Failed)
                {
                    var results = new List<TResult>();
                    do
                    {
                        results.Add(result);
                        result = parser(state);
                    } while (!state.Failed);

                    state.Restore();
                    return new ReadOnlyCollection<TResult>(results);
                }

                return Utils.EmptyCollection<TResult>();
            };
        }

        public static Parser<TToken, ICollection<TResult>> Many1<TToken, TResult>(this Parser<TToken, TResult> parser, string label)
        {
            return state =>
            {
                state.SuspendValuesCollection();

                var result = parser(state);

                if (!state.Failed)
                {
                    var results = new List<TResult>();
                    do
                    {
                        results.Add(result);
                        result = parser(state);
                    } while (!state.Failed);

                    state.Restore();
                    state.ResumeValuesCollection(label); // т.к. many1 допускает больше ожидаемых значений, добавляем его.
                    return new ReadOnlyCollection<TResult>(results);
                }
                else
                {
                    state.ResumeValuesCollection(label);
                    return Utils.EmptyCollection<TResult>();
                }
            };
        }

        public static Parser<TToken, ICollection<TResult>> ManyTill<TToken, TResult, TEnd>(this Parser<TToken, TResult> parser, Parser<TToken, TEnd> end)
        {
            return state =>
                {
                    end(state);

                    if (!state.Failed)
                        return Utils.EmptyCollection<TResult>();

                    var item = parser(state);

                    if (!state.Failed)
                    {
                        var result = new List<TResult>();

                        do
                        {
                            result.Add(item);

                            end(state);

                            if (state.Failed)
                                item = parser(state);
                            else
                                return new ReadOnlyCollection<TResult>(result);

                        } while (!state.Failed);
                    }

                    return Utils.EmptyCollection<TResult>();
                };
        }

        public static Parser<TToken, TResult> Memoize<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
                {
                    MemoizedResult memoizedResult;
                    MemoizationKey memoizationKey = state.CreateMemoizationKey(parser);

                    if (state.TryGetMemoizedResult(memoizationKey, out memoizedResult))
                    {
                        if (state.CurrentBookmark() != memoizedResult.Position)
                        {
                            state.RestoreBookmark(memoizedResult.Position);
                            if (memoizedResult.Failed)
                            {
                                state.Fail();
                                state.ReplaceExceptedValues(memoizedResult.ExceptedValues);
                            }
                            else
                            {
                                state.DiscardErrors(); // otherwise discarding all errors and restoring parser.
                            }
                        }
                        else
                        {
                            if (memoizedResult.Failed)
                            {
                                if (memoizedResult.ExceptedValues != null)
                                    state.Fail(memoizedResult.ExceptedValues);
                                else
                                    state.Fail();
                            }
                            else
                            {
                                state.Restore();
                            }
                        }
                    }
                    else
                    {
                        var startPosition = state.CreateBookmark();

                        if (state.CollectExceptedValues)
                        {
                            var exceptedValues = state.ExceptedValues; // запоминаем и сбрасываем ожидаемые значения, чтобы выделить результат работы указанного парсера
                            state.ReplaceExceptedValues(null);

                            memoizedResult.Result = parser(state);

                            memoizedResult.Position = state.CreateBookmark();

                            if (state.Failed)
                            {
                                memoizedResult.Failed = true;
                                memoizedResult.ExceptedValues = state.ExceptedValues;
                            }

                            if (exceptedValues != null) // восстанавливаем ожидаемые значения
                                state.AppendExceptedValues(exceptedValues);
                        }
                        else
                        {
                            var suspendCount = state.CollectSuspendCount;
                            state.CollectSuspendCount = 0; // возобновляем сбор ожидаемых значений, т.к. не знаем, понадбатся ли они нам при следующих вызовах.

                            memoizedResult.Result = parser(state);
                            memoizedResult.Position = state.CreateBookmark();
                            if (state.Failed)
                            {
                                memoizedResult.Failed = true;
                                memoizedResult.ExceptedValues = state.ExceptedValues;
                            }

                            state.ReplaceExceptedValues(null);
                            state.CollectSuspendCount = suspendCount;
                        }

                        state.MemoizeResult(memoizationKey, memoizedResult);
                    }

                    return (TResult)memoizedResult.Result;
                };
        }

        public static Parser<TToken, TResult> NotFollowedBy<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var bookmark = state.CreateBookmark();

                var result = parser(state);

                state.ResumeValuesCollection();
                state.RestoreBookmark(bookmark);

                if (state.Failed)
                    state.Restore();
                else
                    state.Fail();

                return default(TResult);
            };
        }

        public static Parser<TToken, TResult> Optional<TToken, TResult>(this Parser<TToken, TResult> parser, TResult def = default(TResult))
        {
            return state =>
            {
                var result = parser(state);
                if (state.Failed)
                {
                    state.Restore();
                    return def;
                }
                else
                    return result;
            };
        }

        public static Parser<TToken, TResult> Or<TToken, TResult>(this Parser<TToken, TResult> parser1, Parser<TToken, TResult> parser2)
        {
            return state =>
            {
                var result = parser1(state);
                if (state.Failed)
                    result = parser2(state); // если parser2 сработатет, он сам переключит state.
                return result;
            };
        }

        public static Parser<TToken, TResult> OrEof<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                var result = parser(state);
                if (!state.Failed)
                    return result;
                else if (state.Eof)
                {
                    state.DiscardErrors();
                }

                return default(TResult);
            };
        }

        /// <summary>
        /// Construct parser which returns specified result, if underlying parser succeed.
        /// </summary>
        /// <typeparam name="TToken"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="parser"></param>
        /// <returns></returns>
        public static Parser<TToken, TPure> Pure<TToken, TResult, TPure>(this Parser<TToken, TResult> parser, TPure pureResult)
        {
            return state =>
                {
                    parser(state);
                    if (!state.Failed)
                        return pureResult;
                    else
                        return default(TPure);
                };
        }

        /// <summary>
        /// Run parser and discards result.
        /// </summary>
        /// <typeparam name="TToken"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="parser"></param>
        /// <returns></returns>
        public static Parser<TToken, object> Pure<TToken, TDiscard>(this Parser<TToken, TDiscard> parser)
        {
            return state =>
            {
                parser(state);
                return null;
            };
        }

        public static Parser<TToken, TResult> Try<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var bookmark = state.CreateBookmark();

                var result = parser(state);

                state.ResumeValuesCollection();

                if (state.Failed)
                {
                    state.RestoreBookmark(bookmark);
                }
                else
                {
                    state.CancelBookmark(bookmark);
                }

                return result;
            };
        }

        public static Parser<TToken, TResult> Try<TToken, TResult>(this Parser<TToken, TResult> parser, string label)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var bookmark = state.CreateBookmark();

                var result = parser(state);
                if (state.Failed)
                {
                    state.RestoreBookmark(bookmark);
                    state.ResumeValuesCollection(label);
                }
                else
                {
                    state.CancelBookmark(bookmark);
                    state.ResumeValuesCollection();
                }

                return result;
            };
        }

        /// <summary>
        /// Tries parser, if succeed returns parsed value, or default value otherwise.
        /// Always succeed.
        /// </summary>
        /// <typeparam name="TToken"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="parser"></param>
        /// <returns></returns>
        public static Parser<TToken, TResult> TryOptional<TToken, TResult>(this Parser<TToken, TResult> parser, TResult def = default(TResult))
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var bookmark = state.CreateBookmark();

                var result = parser(state);

                state.ResumeValuesCollection();

                if (state.Failed)
                {
                    state.RestoreBookmark(bookmark);
                    state.Restore();
                    return def;
                }
                else
                {
                    state.CancelBookmark(bookmark);
                    return result;
                }
            };
        }

        /// <summary>
        /// Applies parser, if parser succeed checks result value against the predicate if check fails, fail parsers. Rollback parser position 
        /// on parse or predicate failure.
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <typeparam name="TToken"></typeparam>
        /// <param name="parser"></param>
        /// <param name="predicate"></param>
        /// <param name="label"></param>
        /// <returns></returns>
        /// <remarks>
        /// If you dont want to rollback parser, use <see cref="Validate"/>.
        /// </remarks>
        public static Parser<TToken, TResult> TryValidate<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, bool> predicate, string label)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var bookmark = state.CreateBookmark();

                var result = parser(state);

                state.ResumeValuesCollection();

                if (state.Failed || !predicate(result))
                {
                    state.RestoreBookmark(bookmark);
                    state.Fail(label);
                    return default(TResult);
                }
                else
                {
                    state.CancelBookmark(bookmark);
                    return result;
                }
            };
        }

        public static Parser<TToken, TResult> Satisfy<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, bool> predicate)
        {
            return state =>
            {
                var result = parser(state);

                if (state.Failed || predicate(result))
                {
                    return result;
                }
                else
                {
                    state.Fail();
                    return default(TResult);
                }
            };
        }

        public static Parser<TToken, TResult> Satisfy<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, bool> predicate, IEnumerable<object> exceptedValues)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var result = parser(state);
                state.ResumeValuesCollection();

                if (state.Failed)
                {
                    if (state.CollectExceptedValues)
                        state.AppendExceptedValues(exceptedValues);

                    return result;
                }
                else if (predicate(result))
                {
                    return result;
                }
                else
                {
                    state.Fail(exceptedValues);
                    return default(TResult);
                }
            };
        }

        public static Parser<TToken, ICollection<TResult>> SepBy<TToken, TSeparator, TResult>(this Parser<TToken, TResult> parser, Parser<TToken, TSeparator> separator)
        {
            return state =>
            {
                var item = parser(state);
                if (!state.Failed)
                {
                    var result = new List<TResult>();

                    do
                    {
                        result.Add(item);

                        separator(state);
                        if (state.Failed)
                            break;

                        item = parser(state);
                    } while (!state.Failed);

                    state.Restore();
                    return result;
                }
                else
                {
                    state.Restore();
                    return Utils.EmptyCollection<TResult>();
                }
            };
        }

        public static Parser<TToken, ICollection<TResult>> SepBy1<TToken, TSeparator, TResult>(this Parser<TToken, TResult> parser, Parser<TToken, TSeparator> separator)
        {
            return state =>
            {
                var item = parser(state);
                if (!state.Failed)
                {
                    var result = new List<TResult>();

                    do
                    {
                        result.Add(item);

                        separator(state);
                        if (state.Failed)
                            break;

                        item = parser(state);
                    } while (!state.Failed);

                    state.Restore();
                    return result;
                }
                else
                {
                    return Utils.EmptyCollection<TResult>();
                }
            };
        }

        public static Parser<TToken, ICollection<TResult>> SepBy1<TToken, TSeparator, TResult>(
            this Parser<TToken, TResult> parser,
            Parser<TToken, TSeparator> separator,
            IEnumerable<object> exceptedValues,
            IEnumerable<object> exceptedSeparators)
        {
            return state =>
            {
                state.SuspendValuesCollection();
                var item = parser(state);
                if (!state.Failed)
                {
                    var result = new List<TResult>();

                    do
                    {
                        result.Add(item);

                        separator(state);
                        if (state.Failed)
                        {
                            state.ResumeValuesCollection(exceptedSeparators);
                            break;
                        }

                        item = parser(state);
                        if (state.Failed)
                        {
                            state.ResumeValuesCollection(exceptedValues);
                            break;
                        }
                    } while (true);

                    state.Restore();
                    return result;
                }
                else
                {
                    state.ResumeValuesCollection(exceptedValues);
                    return Utils.EmptyCollection<TResult>();
                }
            };
        }

        /// <summary>
        /// Applies two parsers in sequence and returns result of the first parser.
        /// </summary>
        /// <param name="parser1"></param>
        /// <param name="parser2"></param>
        /// <returns></returns>
        public static Parser<TToken, TResult1> SeqLeft<TToken, TResult1, TResult2>(this Parser<TToken, TResult1> parser1, Parser<TToken, TResult2> parser2)
        {
            return state =>
            {
                var result = parser1(state);
                if (!state.Failed)
                {
                    parser2(state);
                    return result;
                }
                else
                {
                    return default(TResult1);
                }
            };
        }

        /// <summary>
        /// Applies two parsers in sequence and returns result of the last parser.
        /// </summary>
        /// <param name="parser1"></param>
        /// <param name="parser2"></param>
        /// <returns></returns>
        public static Parser<TToken, TResult2> SeqRight<TToken, TResult1, TResult2>(this Parser<TToken, TResult1> parser1, Parser<TToken, TResult2> parser2)
        {
            return state =>
            {
                parser1(state);
                if (!state.Failed)
                {
                    return parser2(state);
                }
                else
                {
                    return default(TResult2);
                }
            };
        }

        /// <summary>
        /// Applies two parsers in sequence and discards the result.
        /// </summary>
        /// <param name="parser1"></param>
        /// <param name="parser2"></param>
        /// <returns></returns>
        public static Parser<TToken, object> SeqSkip<TToken, TResult1, TResult2>(this Parser<TToken, TResult1> parser1, Parser<TToken, TResult2> parser2)
        {
            return state =>
                {
                    parser1(state);
                    if (!state.Failed)
                        parser2(state);
                    return null;
                };
        }

        public static Parser<TToken, object> SkipMany<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                do
                {
                    parser(state);
                } while (!state.Failed);

                state.Restore();
                return null;
            };
        }

        public static Parser<TToken, object> SkipMany1<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                parser(state);
                if (state.Failed)
                    return null;
                do
                {
                    parser(state);
                } while (!state.Failed);

                state.Restore();
                return null;
            };
        }

        /// <summary>
        /// Skips input tokens till the parser succeed. Fails if EOF reached without success.
        /// </summary>
        /// <typeparam name="TToken"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="parser"></param>
        /// <returns>
        /// Parse result.
        /// </returns>
        public static Parser<TToken, TResult> SkipTill<TToken, TResult>(this Parser<TToken, TResult> parser)
        {
            return state =>
            {
                TResult result;
                while (true)
                {
                    result = parser(state);

                    if (state.Failed && !state.Eof)
                        state.Next(); // вроде это одно и то же, вызов AnyToken - лишнее.
                    //PrimParsers.AnyToken<TToken>()(state);
                    else
                        break;
                }
                return result;
            };
        }

        /// <summary>
        /// Applies parser, if parser succeed checks result value against the predicate if check fails, fail parsers. Does not rollback.
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <typeparam name="TToken"></typeparam>
        /// <param name="parser"></param>
        /// <param name="predicate"></param>
        /// <param name="label"></param>
        /// <returns></returns>
        /// <remarks>
        /// Use <see cref="TryValidate"/> if you want to rollback parser to start position if predicate fails.
        /// </remarks>
        public static Parser<TToken, TResult> Validate<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, bool> predicate)
        {
            return state =>
            {
                var result = parser(state);

                if (!state.Failed && !predicate(result))
                {
                    state.Fail();
                    return default(TResult);
                }
                else
                    return result;
            };
        }

        /// <summary>
        /// Applies parser, if parser succeed checks result value against the predicate if check fails, fail parsers. Does not rollback.
        /// </summary>
        /// <typeparam name="TToken">Parser token type</typeparam>
        /// <typeparam name="TResult">Parse result type</typeparam>
        /// <param name="parser">Parser</param>
        /// <param name="predicate">Predicate to check parse result</param>
        /// <param name="exceptedValues">Excepted values for error reporting</param>
        /// <returns>
        /// Use <see cref="TryValidate"/> if you want to rollback parser to start position if predicate fails.
        /// Error collection:
        /// First specified parser will be tried. If parser fails, parse errors will be preserved, if parser succeed, it result will be checked against predicate.
        /// If check fails, <typeparamref name="exceptedValues"/> will be added to exceptedValues collection.
        /// </returns>
        public static Parser<TToken, TResult> Validate<TToken, TResult>(this Parser<TToken, TResult> parser, Func<TResult, bool> predicate, IEnumerable<object> exceptedValues)
        {
            return state =>
            {
                var result = parser(state);

                if (!state.Failed && !predicate(result))
                {
                    state.Fail(exceptedValues);
                    return default(TResult);
                }
                else
                    return result;
            };
        }
    }
}